﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using BomberManServer.Gaming.Object.Mapping.Items;
using BomberManServer.Gaming.Object.Mapping;

namespace BomberManServer.Utils
{
    /// <summary>
    /// Coordinates allow to manage 3 sets of coordinnates : 
    ///         - the blocks 
    ///             It is the "rough" coordinates indicating wich block contains the Item
    ///         - the blockDivided
    ///             It is the Visual coordinates, indicating the location of the item on the block
    ///         - the speed adapted
    ///             Used for speed adjustment
    ///             
    /// Need the System.configuration reference !!!
    /// </summary>
    public class Coordinates
    {
        #region Coordinates attributes

        private UInt32 _xBlock;
        private UInt32 _yBlock;

        public UInt32 xBlock { get { return _xBlock; } private set { xTheoricPoint = value * Constants.instance.theoricPointsPerBlock; } }
        public UInt32 yBlock { get { return _yBlock; } private set { yTheoricPoint = value * Constants.instance.theoricPointsPerBlock; } }

        private UInt32 _xPoint;
        private UInt32 _yPoint;

        public UInt32 xPoint { get { return _xPoint; } private set { xTheoricPoint = value * Constants.instance.pointsPerBlock; } }
        public UInt32 yPoint { get { return _yPoint; } private set { yTheoricPoint = value * Constants.instance.pointsPerBlock; } }

        public UInt32 _xTheoricPoint;
        public UInt32 _yTheoricPoint;

        public UInt32 xTheoricPoint { get { return _xTheoricPoint; } set { this._xTheoricPoint = value; this._xPoint = value / Constants.instance.pointsPerBlock; this._xBlock = value / Constants.instance.theoricPointsPerBlock; } }
        public UInt32 yTheoricPoint { get { return _yTheoricPoint; } set { this._yTheoricPoint = value; this._yPoint = value / Constants.instance.pointsPerBlock; this._yBlock = value / Constants.instance.theoricPointsPerBlock; } }

        #endregion Coordinates attributes

        //All the different kinds of constructors for Coordinates
        #region Constructors

        private Coordinates() {}

        public Coordinates(Coordinates coord)
        {
            this.xTheoricPoint = coord.xTheoricPoint;
            this.yTheoricPoint = coord.yTheoricPoint;
        }
        
        public static Coordinates fromBlock(UInt32 x, UInt32 y)
        {
            Coordinates coord = new Coordinates();
            coord.xTheoricPoint = x * Constants.instance.theoricPointsPerBlock + Constants.instance.theoricPointsPerBlock / 2;
            coord.yTheoricPoint = y * Constants.instance.theoricPointsPerBlock + Constants.instance.theoricPointsPerBlock / 2;
            return coord;
        }

        public static Coordinates fromPoint(uint x, uint y)
        {
            Coordinates coord = new Coordinates();
            coord.xTheoricPoint = x * Constants.instance.pointsPerBlock;
            coord.yTheoricPoint = y * Constants.instance.pointsPerBlock;
            return coord;
        }

        public static Coordinates fromTheoricPoint(uint x, uint y)
        {
            Coordinates coord = new Coordinates();
            coord.xTheoricPoint = x;
            coord.yTheoricPoint = y;
            return coord;
        }

        public static Coordinates centered(Coordinates coord)
        {
            return Coordinates.fromTheoricPoint(coord.xBlock * Constants.instance.theoricPointsPerBlock + Constants.instance.theoricPointsPerBlock / 2, coord.yBlock * Constants.instance.theoricPointsPerBlock + Constants.instance.theoricPointsPerBlock / 2);
        }

        public static Coordinates sidedCoord(Coordinates coord, Direction direction)
        {
            Coordinates coordSided = Coordinates.centered(coord);
            coordSided.hugSide(direction);
            return coordSided;
        }

        #endregion Constructors

        #region Operations

        public void updatePoint(uint x, uint y)
        {
            xTheoricPoint = x * Constants.instance.pointsPerBlock; ;
            yTheoricPoint = y * Constants.instance.pointsPerBlock; ;
        }

        public void updateTheoricPoint(uint x, uint y)
        {
            xTheoricPoint = x;
            yTheoricPoint = y;
        }

        public bool hugSide(Direction direction)
        {
            Coordinates coord = new Coordinates(this);

            switch (direction)
            {
                case Direction.UP:
                    this.yTheoricPoint = coord.yBlock * Constants.instance.theoricPointsPerBlock + Constants.instance.avatarSize * Constants.instance.pointsPerBlock;
                    break;
                case Direction.DOWN:
                    this.yTheoricPoint = coord.yBlock * Constants.instance.theoricPointsPerBlock + (Constants.instance.pointsPerBlock - 1 - Constants.instance.avatarSize) * Constants.instance.pointsPerBlock;
                    break;
                case Direction.RIGHT:
                    this.xTheoricPoint = coord.xBlock * Constants.instance.theoricPointsPerBlock + (Constants.instance.pointsPerBlock - 1 - Constants.instance.avatarSize) * Constants.instance.pointsPerBlock;
                    break;
                case Direction.LEFT:
                    this.xTheoricPoint = coord.xBlock * Constants.instance.theoricPointsPerBlock + Constants.instance.avatarSize * Constants.instance.pointsPerBlock;
                    break;
                default:
                    break;
            }
            return !coord.equalPoint(this);

        }

        public bool equalBlock(Coordinates coord)
        {
            if (this.xBlock == coord.xBlock && this.yBlock == coord.yBlock)
                return true;
            else
                return false;
        }

        public bool equalPoint(Coordinates coord)
        {
            if (this.xPoint == coord.xPoint && this.yPoint == coord.yPoint)
                return true;
            else
                return false;
        }

        public bool equalTheoric(Coordinates coord)
        {
            if (this.xTheoricPoint == coord.xTheoricPoint && this.yTheoricPoint == coord.yTheoricPoint)
                return true;
            else
                return false;
        }

        public Coordinates translate(Direction direction, UInt32 step)
        {
            Coordinates coord = new Coordinates(this);
            switch (direction)
            {
                case Direction.DOWN:
                    coord.yTheoricPoint += step;
                    break;
                case Direction.UP:
                    coord.yTheoricPoint -= step;
                    break;
                case Direction.LEFT:
                    coord.xTheoricPoint -= step;
                    break;
                case Direction.RIGHT:
                    coord.xTheoricPoint += step;
                    break;
            }
            return coord;
        }

        //Obsolete Operation
        /*
        public void getOutOfBlock(Direction dir)
        {
            switch (dir)
            {
                case Direction.DOWN:
                    this.yBlock--;
                    this.hugSide(Direction.DOWN);
                    break;
                case Direction.UP:
                    this.yBlock++;
                    this.hugSide(Direction.UP);
                    break;
                case Direction.RIGHT:
                    this.xBlock--;
                    this.hugSide(Direction.RIGHT);
                    break;
                case Direction.LEFT:
                    this.xBlock++;
                    this.hugSide(Direction.LEFT);
                    break;

            }
        }

        public bool changeBlock(Avatar avatar, Direction direction)
        {
            switch (direction)
            {
                case Direction.UP:
                    if (this.yTheoricPoint - avatar.speed - Constants.instance.avatarSize < Constants.instance.theoricPointsPerBlock * (yBlock - 1))
                        return true;
                    else
                        return false;
                case Direction.DOWN:
                    if (this.yTheoricPoint + avatar.speed + Constants.instance.avatarSize > Constants.instance.theoricPointsPerBlock * (yBlock + 1))
                        return true;
                    else
                        return false;
                case Direction.LEFT:
                    if (this.xTheoricPoint - avatar.speed - Constants.instance.avatarSize < Constants.instance.theoricPointsPerBlock * (xBlock - 1))
                        return true;
                    else
                        return false;
                case Direction.RIGHT:
                    if (this.xTheoricPoint + avatar.speed + Constants.instance.avatarSize < Constants.instance.theoricPointsPerBlock * (xBlock + 1))
                        return true;
                    else
                        return false;
            }
            return true;
        }

        */


        public Coordinates getNextBlockCoord(Direction direction)
        {
            switch (direction)
            {
                case Direction.UP:
                    return Coordinates.fromBlock(this.xBlock, this.yBlock - 1);
                case Direction.DOWN:
                    return Coordinates.fromBlock(this.xBlock, this.yBlock + 1);
                case Direction.LEFT:
                    return Coordinates.fromBlock(this.xBlock - 1, this.yBlock);
                case Direction.RIGHT:
                    return Coordinates.fromBlock(this.xBlock + 1, this.yBlock);
            }
            return null;
        }

        #endregion Operations

        #region Utils


        public Direction getDirectionTo(Coordinates coord)
        {
            int x;
            int y;
            
            x = int.Parse(this.xPoint.ToString()) - int.Parse(coord.xPoint.ToString());
            y = int.Parse(this.yPoint.ToString()) - int.Parse(coord.yPoint.ToString());

            // smaller number means axis (x or y) is the chosen one
            bool xClosest;
            if (x == 0)
                xClosest = false;
            else
                if (y == 0)
                    xClosest = true;
                else
                    xClosest = (Math.Abs(x) < Math.Abs(y));

            if (xClosest)
                if (x < 0)
                    return Direction.RIGHT; // the other coord has greater x -> to the right
                else
                    return Direction.LEFT;
            else
                if (y < 0)
                    return Direction.DOWN;
                else
                    return Direction.UP;
        }

        //Obsolete Utils
        /*
        public UInt32 distance(Coordinates coord)
        {

            if(this.equalBlock(coord))
                return 0;

            UInt32 distance = 0;
            UInt32 x = coord.xPoint - coord.xBlock * Constants.instance.pointsPerBlock;
            UInt32 y = coord.yPoint - coord.yBlock * Constants.instance.pointsPerBlock;
            switch (getDirectionTo(coord))
            {
                case Direction.UP :
                    distance = Constants.instance.pointsPerBlock - y + Constants.instance.avatarSize;
                    distance += additionalDistance(x);
                    break;
                case Direction.DOWN:
                    distance = Constants.instance.pointsPerBlock - y + Constants.instance.avatarSize;
                    distance += additionalDistance(x);
                    break;
                case Direction.LEFT:
                    distance = Constants.instance.pointsPerBlock - x + Constants.instance.avatarSize;
                    distance += additionalDistance(y);
                    break;
                case Direction.RIGHT:
                    distance = Constants.instance.pointsPerBlock - y + Constants.instance.avatarSize;
                    distance += additionalDistance(y);
                    break;

            }


            return distance;
        }
        
        public UInt32 additionalDistance(UInt32 x)
        {
            if (x < 3)
                return Constants.instance.avatarSize - x + 1;
            if (x > 6)
                return x + Constants.instance.avatarSize - Constants.instance.pointsPerBlock;
            return 0;
        }

        public Coordinates selectClosest(List<Coordinates> coords)
        {
            Coordinates coordTemp = null;
            UInt32 distance = Constants.instance.pointsPerBlock;

            foreach (Coordinates coord in coords)
            {
                if (this.distance(coord) < distance)
                {
                    distance = this.distance(coord);
                    coordTemp = coord;
                }
            }
            return coordTemp;
        }
        
        public Direction getDirectionToClosest()
        {
            int x;
            int y;

            x = int.Parse(this.xPoint.ToString()) - int.Parse((Constants.instance.pointsPerBlock * this.xBlock).ToString());
            y = int.Parse(this.yPoint.ToString()) - int.Parse((Constants.instance.pointsPerBlock * this.yBlock).ToString());

            bool xClosestRight = (x > 6);
            if (x > 6)
                return Direction.RIGHT;
            if (x < 3)
                return Direction.LEFT;
            if (y > 6)
                return Direction.DOWN;
            else
                return Direction.UP;
        }
        */
        #endregion Utils

        #region glide

        public bool glideable(Direction direction, Map map,Coordinates[] sideCoord)
        {
            UInt32 x = this.xPoint - Constants.instance.pointsPerBlock * this.xBlock;
            UInt32 y = this.yPoint - Constants.instance.pointsPerBlock * this.yBlock;

            switch (direction)
            {
                case Direction.DOWN:
                    return ((((x < Constants.instance.pointsGlide) && (0 < x)) || ((x > Constants.instance.pointsPerBlock - Constants.instance.pointsGlide -1) && (x < Constants.instance.pointsPerBlock -1))) && (map.blocks.check(sideCoord[1])) && (map.getBlockAt(sideCoord[1]).item.free));
                case Direction.UP:
                    return ((((x < Constants.instance.pointsGlide) && (0 < x)) || ((x > Constants.instance.pointsPerBlock - Constants.instance.pointsGlide -1) && (x < Constants.instance.pointsPerBlock -1))) && (map.blocks.check(sideCoord[0])) && (map.getBlockAt(sideCoord[0]).item.free));
                case Direction.LEFT:
                    return ((((y < Constants.instance.pointsGlide) && (0 < y)) || ((y > Constants.instance.pointsPerBlock - Constants.instance.pointsGlide -1) && (y < Constants.instance.pointsPerBlock -1))) && (map.blocks.check(sideCoord[2])) && (map.getBlockAt(sideCoord[2]).item.free));
                case Direction.RIGHT:
                    return ((((y < Constants.instance.pointsGlide) && (0 < y)) || ((y > Constants.instance.pointsPerBlock - Constants.instance.pointsGlide -1) && (y < Constants.instance.pointsPerBlock -1))) && (map.blocks.check(sideCoord[3])) && (map.getBlockAt(sideCoord[3]).item.free));
            }
            return false;
        }

        public Coordinates glide(Direction direction, Avatar avatar)
        {
            // gerer que les coordonnées souhaité sont valide (pas un mur ou bomb ... )

            Direction glideDirection = this.getGlideDirection(direction);
            Coordinates coord = this.translate(glideDirection, avatar.speed);
            //if(isCoordGlideable(avatar, direction))
                return coord;
        }

        public Direction getGlideDirection(Direction originalDir)
        {
            UInt32 x = this.xPoint - Constants.instance.pointsPerBlock * this.xBlock;
            UInt32 y = this.yPoint - Constants.instance.pointsPerBlock * this.yBlock;

            switch (originalDir)
            {
                case Direction.DOWN:
                     if((x < Constants.instance.pointsGlide) && (x > 0))
                         return Direction.RIGHT;
                     if ((x > Constants.instance.pointsPerBlock - Constants.instance.pointsGlide - 1) && (x < Constants.instance.pointsPerBlock - 1))
                         return Direction.LEFT;
                     break;
                case Direction.UP:
                     if ((x < Constants.instance.pointsGlide)&& (x > 0))
                         return Direction.RIGHT;
                     if ((x > Constants.instance.pointsPerBlock - Constants.instance.pointsGlide -1) && (x < Constants.instance.pointsPerBlock - 1))
                         return Direction.LEFT;
                     break;
                case Direction.LEFT:
                     if ((y < Constants.instance.pointsGlide)&& (y > 0))
                         return Direction.DOWN;
                     if ((y > Constants.instance.pointsPerBlock - Constants.instance.pointsGlide -1) && (y < Constants.instance.pointsPerBlock - 1))
                         return Direction.UP;
                     break;
                case Direction.RIGHT:
                     if ((y < Constants.instance.pointsGlide)&& (y > 0))
                         return Direction.DOWN;
                     if ((y > Constants.instance.pointsPerBlock - Constants.instance.pointsGlide -1) && (y < Constants.instance.pointsPerBlock - 1))
                         return Direction.UP;
                     break;
            }
            return Direction.RIGHT;
        }

        public Block getGlideTargetBlock(Coordinates coord, Avatar avatar)
        {
            return avatar.game.map.getBlockAt(coord);
        }
        #endregion glide
    }
}
